// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//


/* this camera is intended to be used in conjunction with some other
camera "sourceCamera" for controlling that camera's lights.  This
camera has a notion of the "sceneShape", which is the world shape from
the other camera.  It interrogates this shape for it's bounding box
and uses it to draw a box where the shape is.  It draws this box (in
wireframe) and then it draws a plane twice the size of the box in each
of the three orthogonal axes.
*/

#import "WW3DLight.h"
#import "WW3DShader.h"
#import "WWLightInspectorCamera.h"
#import "WWLightInspectorShape.h"
#import "WWDADLightView.h"

@implementation WWLightInspectorCamera

+ initialize { return [WWLightInspectorCamera setVersion:1], self; }

- initFrame:(const NXRect *)r 
{
   RtPoint  fromP = {5, 5, -10.0}, toP = {0, 0, 0};
   id       aShape, secondLevelShape, thirdLevelShape;
   const char  *WDRAG_PBTYPE[] = { NXAsciiPboardType, NULL};


   [super initFrame:r];
   [self registerForDraggedTypes:WDRAG_PBTYPE count:1];

   renderStyle = N3D_SmoothSolids;
   movingRenderStyle = N3D_WireFrame;

   scaleUpFactor = 1.05;
   scaleDownFactor = 0.95;
   translateXFactor = 0.025;
   translateYFactor = 0.025;
   translateZFactor = 0.05;
   epsilon = 4.0;
   selectionWidthEpsilon = 8.0;
   selectionHeightEpsilon = 8.0;
   showSelectedShape = NO;
   drawOriginForSelectedShape = NO;
   
   // setup 3D camera stuff
   [self setEyeAt:fromP toward:toP roll:0.0];

  //[self setupDefaultLights];
  // !!! need to get lights from sourceCamera
  // here in init, though, we probably don't have any...

  // setup some initial global rendering state
  // note that this fucks up the 3DWell - yikes!
  //[self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
  lowRezTesselationVector[0] = lowRezTesselationVector[1] = 4.0;
  tesselationVector[0] = tesselationVector[1] = 8.0;
  //RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  // stick in a reasonable shader
  theShader=[[WW3DShader alloc] init];
  [theShader setUseColor:YES];
  [theShader setColor:ribColor];
  [(N3DShader *)theShader setShader:"plastic"];

  // stick in a reasonable reference shape
  aShape = [[N3DShape alloc] init];
  [aShape setShapeName:NXCopyStringBuffer("lightWorld")];
  [[self setWorldShape:aShape] free]; // free the default world shape

  secondLevelShape = [[N3DShape alloc] init];
  [[self worldShape] linkDescendant:secondLevelShape];
  thirdLevelShape = [[WWLightInspectorShape alloc] init];
  [aShape setShapeName:NXCopyStringBuffer("boundingBoxOfWellWorld")];
  //[aShape calculateBoundingBox];
  [secondLevelShape linkDescendant:thirdLevelShape];

  // now when we add lights as descendants of the worldShape, they
  // will illuminate the bounding box of the well world...

  [self setSurfaceTypeForAll:N3D_SmoothSolids chooseHider:YES];

  theRotator = [[N3DRotator alloc] initWithCamera:self];
  [theRotator setRotationAxis:N3D_AllAxes];
  trackballAffects = WW_TRACKBALL_CAMERA;

  return self;
}

// the receptor gets sent this message when the object is dragged into
// the File window, and when it is unarchived from the nib file.
- awake
{
  const char  *WDRAG_PBTYPE[] = { NXAsciiPboardType, NULL};


  [super awake];
  [self registerForDraggedTypes:WDRAG_PBTYPE count:1];

  // everything else has just been unarchived...

  theRotator = [[N3DRotator alloc] initWithCamera:self];
  [theRotator setRotationAxis:N3D_AllAxes];
  trackballAffects = WW_TRACKBALL_CAMERA;

  return self;
}

- free

{
  [self unregisterDraggedTypes];
  return [super free];
}


/////////////////////// 
// 3D stuff
/////////////////////// 
#define ACTIVEBUTTONMASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_ALTERNATEMASK|NX_CONTROLMASK|NX_COMMANDMASK)
- mouseDown:(NXEvent *)theEvent
{
  int        oldMask;
  //NXPoint    p, last, start;
  //NXRect     visibleRect, oldRegion;
  NXPoint    start;
  RtPoint    myEyePoint, 
             previousEyePoint, previousViewPoint;
  float      previousRoll;


  dMouse.x = 0.0;
  dMouse.y = 0.0;

  // track the mouse until a mouseUp event occurs, updating the display
  // as tracking happens.
  [self lockFocus];
  oldMask = [window addToEventMask:ACTIVEBUTTONMASK];
  
  // switch to the N3D_WireFrame surface type
  [self setSurfaceTypeForAll:movingRenderStyle chooseHider:YES];	
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, lowRezTesselationVector, RI_NULL);
  
  oldMouse = theEvent->location;
  [self convertPoint:&oldMouse fromView:nil];
  start = oldMouse;

  // when the alt key is depressed, we scale the worldShape up or down, depending on mouse movement.
  // otherwise, we rotate the worldShape using the virtual trackball

  while (1)
  { newMouse = theEvent->location;
    [self convertPoint:&newMouse fromView:nil];
    dMouse.x = newMouse.x - oldMouse.x;
    dMouse.y = newMouse.y - oldMouse.y;
    if (dMouse.x != 0.0 || dMouse.y != 0.0) 
    {  if (theEvent->flags & NX_ALTERNATEMASK)
       {  switch (trackballAffects)
	  {  case WW_TRACKBALL_WORLD_SHAPE:
		 [worldShape translate:0.0 :0.0 :(translateZFactor * dMouse.y)];
		 break;
	     case WW_TRACKBALL_CAMERA:
		 // need to change Eye point
		 [self moveEyeBy:0.0 :0.0 :(-1. * (translateZFactor * dMouse.y))];
		 break;
	  }
       }
       else
       {  
	     if (theEvent->flags & NX_COMMANDMASK)
             {  switch (trackballAffects)
	  	{  case WW_TRACKBALL_WORLD_SHAPE:
		    [worldShape translate:(translateXFactor * dMouse.x) 
                                         :(translateYFactor * dMouse.y) :0.0];
		    break;
	          case WW_TRACKBALL_CAMERA:
                    [self moveEyeBy:(-1. * (translateXFactor * dMouse.x)) 
 	 	                   :(-1. * (translateYFactor * dMouse.y)) :0.0];
		    break;
	        }
	     }
             else
             {  [theRotator trackMouseFrom:&oldMouse to:&newMouse rotationMatrix:rmat andInverse:irmat];
                switch (trackballAffects)
		{  case WW_TRACKBALL_WORLD_SHAPE:
		       [worldShape concatTransformMatrix:rmat premultiply:YES];
                       break;
		   case WW_TRACKBALL_CAMERA:
		       [self getEyeAt:&previousEyePoint toward:&previousViewPoint roll:&previousRoll];   
                       myEyePoint[0] = previousEyePoint[0] + (-1. * (translateXFactor * dMouse.x)); 
                       myEyePoint[1] = previousEyePoint[1] + (-1. * (translateYFactor * dMouse.y)); 
                       myEyePoint[2] = previousEyePoint[2];
		       [self setEyeAt:myEyePoint toward:previousViewPoint roll:previousRoll];   

                       break;
		}
	     } 
       }
       [self display];
       NXPing();
    }
    theEvent = [NXApp getNextEvent:ACTIVEBUTTONMASK];
    if (theEvent->type == NX_MOUSEUP)
    {  break;
    }
    else
    {  oldMouse = newMouse;
    }
  }

  // check to see if we should go off and render (need a minimum image size, like 16x16)...

  [self setSurfaceTypeForAll:renderStyle chooseHider:YES];	
  RiGeometricApproximation(RI_TESSELATION, RI_PARAMETRIC, tesselationVector, RI_NULL);

  [self display];
  [self unlockFocus];
  [window setEventMask:oldMask];
  [window flushWindow];

  return self;
}

- (BOOL)acceptsFirstResponder { return YES; }

- (BOOL)acceptsFirstMouse { return YES; }

- (int)trackballAffects { return trackballAffects; }
- takeTrackballAffectsFromMatrix:sender
{
  int  newVal = [[sender selectedCell] tag];


  switch (newVal)
  {  case WW_TRACKBALL_WORLD_SHAPE:
	 trackballAffects = newVal;
         [self setUsePreTransformMatrix:NO];
	 break;
     case WW_TRACKBALL_CAMERA:
	 // we assume that [self usesPreTransformMatrix] returns YES...
	 trackballAffects = newVal;
         [self setUsePreTransformMatrix:YES];
 	 break;
     default:
	 NXLogError("%d is an invalid value for trackballAffects.\n", newVal);
     } 


  return self;
}

- (int)trackballXYZ 
{ 
  N3DAxis  theAxis  = [theRotator rotationAxis];
  int      theAxisInt = -1;


  if (theAxis == N3D_AllAxes) { theAxisInt = 0; }

  if (theAxis == N3D_XAxis) { theAxisInt = 1; }
  if (theAxis == N3D_YAxis) { theAxisInt = 2; }
  if (theAxis == N3D_ZAxis) { theAxisInt = 3; }

  if (theAxis == N3D_XYAxes) { theAxisInt = 4; }
  if (theAxis == N3D_XZAxes) { theAxisInt = 5; }
  if (theAxis == N3D_YZAxes) { theAxisInt = 6; }
 
  return theAxisInt;
}

- takeTrackballXYZFromMatrix:sender
{
  int  newVal = [[sender selectedCell] tag];

  switch (newVal)
  {  case 0:
	 [theRotator setRotationAxis:N3D_AllAxes];
	 break;
     case 1:
	 [theRotator setRotationAxis:N3D_XAxis];
	 break;
     case 2:
	 [theRotator setRotationAxis:N3D_YAxis];
	 break;
     case 3:
	 [theRotator setRotationAxis:N3D_ZAxis];
	 break;
     case 4:
	 [theRotator setRotationAxis:N3D_XYAxes];
	 break;
     case 5:
	 [theRotator setRotationAxis:N3D_XZAxes];
	 break;
     case 6:
	 [theRotator setRotationAxis:N3D_YZAxes];
	 break;
     default:
	 NXLogError("%d is an invalid value for trackballXYZ.\n", newVal);
     } 
  return self;
}


- (NXColor) backgroundColor 	{ return backgroundColor; }
- setBackgroundColor:(NXColor)c { backgroundColor = c; [self display]; return self; }

////////////////////////////////////////////////////////////////////
//			Dragging stuff
///////////////////////////////////////////////////////////////////


- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
    if ([[sender draggingSource] respondsTo:@selector(lightType)])
    {  return NX_DragOperationCopy;
    }
    return NX_DragOperationNone;
}

- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
    if ([[sender draggingSource] respondsTo:@selector(lightType)])
    {  return NX_DragOperationCopy;
    }
    return NX_DragOperationNone;
}

- draggingExited:sender { return self; }
- (BOOL)prepareForDragOperation:sender  { return YES; }

- (BOOL)performDragOperation:sender
{
    RtPoint fromPoint = {1, 1, -1};
    RtPoint toPoint = {0, 0, 0};
    RtFloat  coneAngle = 30.0, 
             coneDelta = 5.0, 
             beamDistribution = 2.0, 
             intensity = 0.7;
    id       newLight;
    NXEvent  *event = [NXApp currentEvent];
    NXPoint  locationInWindow;
    NXRect   myFrame;


    locationInWindow.x = event->location.x;
    locationInWindow.y = event->location.y;
    NXLogError("locationInWindow == (%f %f)\n", locationInWindow.x, locationInWindow.y);
    [self convertPoint:&locationInWindow fromView:[[self window] contentView]];
    NXLogError("location in this view == (%f %f)\n", locationInWindow.x, locationInWindow.y);
    [self getFrame:&myFrame];
    NXLogError("my frame is (%f %f) and starts at (%f %f)\n", 
               myFrame.size.width, myFrame.size.height, myFrame.origin.x, myFrame.origin.y);
    NXLogError("normalized, the drop happened at (%f %f)\n", 
               (locationInWindow.x/myFrame.size.width), (locationInWindow.y/myFrame.size.height)); 

    if ([[sender draggingSource] respondsTo:@selector(lightType)])
    {  // send lightType, malloc up a new one, figure out where the mouse is and drop the light there
       switch ((int)[[sender draggingSource] lightType])
       {  case ((int)N3D_PointLight):    newLight = [[WW3DLight alloc] init];
	                                 [newLight makePointFrom:fromPoint intensity:intensity];
	                                 [newLight translate:N3D_XComp(fromPoint) :N3D_YComp(fromPoint) :N3D_ZComp(fromPoint)];
	                                 [[self worldShape] linkDescendant:newLight];
 	                                 break;
          case ((int)N3D_DistantLight):  newLight = [[WW3DLight alloc] init];
	                                 [newLight makeDistantFrom:fromPoint to:toPoint intensity:intensity];
	                                 [newLight translate:N3D_XComp(fromPoint) :N3D_YComp(fromPoint) :N3D_ZComp(fromPoint)];
	                                 [[self worldShape] linkDescendant:newLight];
	                                 break;
          case ((int)N3D_SpotLight):     newLight = [[WW3DLight alloc] init];
	                                 [newLight makeSpotFrom:fromPoint to:toPoint 
                                                   coneAngle:coneAngle coneDelta:coneDelta 
                                                   beamDistribution:beamDistribution 
                                                   intensity:intensity];
	                                 [newLight translate:N3D_XComp(fromPoint) :N3D_YComp(fromPoint) :N3D_ZComp(fromPoint)];
   	                                 [[self worldShape] linkDescendant:newLight];
	                                 break;
          default:                       newLight = [[WW3DLight alloc] init];
	                                 [[self worldShape] linkDescendant:newLight];
	                                 break;
        }
        return YES;
    }
    return NO;
}


- concludeDragOperation:sender {  return self; }

#define typeVector "fff[2f][2f]"
#define typeValues &translateXFactor, &translateYFactor, &translateZFactor, &lowRezTesselationVector, &tesselationVector


- read:(NXTypedStream *)stream {
    int version;
    [super read:stream];

    version = NXTypedStreamClassVersion(stream,"WWLightCamera");
    if (version == 0) NXReadTypes(stream,"i",&version), version=1;
    if (version == 1)
    {  NXReadTypes(stream, typeVector, typeValues);
       backgroundColor = NXReadColor(stream);
    }
    return self;
}

- write:(NXTypedStream *)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteColor(stream, backgroundColor);
    return self;
}


@end
